/**
* XMLHttpRequest.js Copyright (C) 2011 Sergey Ilinsky (http://www.ilinsky.com)
*
* This work is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or
* (at your option) any later version.
*
* This work is distributed in the hope that it will be useful,
* but without any warranty; without even the implied warranty of
* merchantability or fitness for a particular purpose. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this library; if not, write to the Free Software Foundation, Inc.,
* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/

(function () {

    // Save reference to earlier defined object implementation (if any)
    var oXMLHttpRequest = window.XMLHttpRequest;

    // Define on browser type
    var bGecko  = !!window.controllers;
    var bIE     = !!window.document.namespaces;
    var bIE7    = bIE && window.navigator.userAgent.match(/MSIE 7.0/);

    // Enables "XMLHttpRequest()" call next to "new XMLHttpRequest()"
    function fXMLHttpRequest() {
        if (!window.XMLHttpRequest || bIE7) {
            this._object = new window.ActiveXObject("Microsoft.XMLHTTP");
        } // only use initial XHR object internally if current reference to XHR is our normalized replacement
        else if (window.XMLHttpRequest.isNormalizedObject) {
            this._object = new oXMLHttpRequest();
        } // otherwise use whatever is currently referenced by XMLHttpRequest
        else {
            this._object = new window.XMLHttpRequest();
        }
        this._listeners = [];
    }

    // Constructor
    function cXMLHttpRequest() {
        return new fXMLHttpRequest;
    }
    cXMLHttpRequest.prototype = fXMLHttpRequest.prototype;

    // BUGFIX: Firefox with Firebug installed would break pages if not executed
    if (bGecko && oXMLHttpRequest.wrapped) {
        cXMLHttpRequest.wrapped = oXMLHttpRequest.wrapped;
    }

    // Marker to be able to easily identify our object
    cXMLHttpRequest.isNormalizedObject = true;

    // Constants
    cXMLHttpRequest.UNSENT            = 0;
    cXMLHttpRequest.OPENED            = 1;
    cXMLHttpRequest.HEADERS_RECEIVED  = 2;
    cXMLHttpRequest.LOADING           = 3;
    cXMLHttpRequest.DONE              = 4;

    // Interface level constants
    cXMLHttpRequest.prototype.UNSENT            = cXMLHttpRequest.UNSENT;
    cXMLHttpRequest.prototype.OPENED            = cXMLHttpRequest.OPENED;
    cXMLHttpRequest.prototype.HEADERS_RECEIVED  = cXMLHttpRequest.HEADERS_RECEIVED;
    cXMLHttpRequest.prototype.LOADING           = cXMLHttpRequest.LOADING;
    cXMLHttpRequest.prototype.DONE              = cXMLHttpRequest.DONE;

    // Public Properties
    cXMLHttpRequest.prototype.readyState    = cXMLHttpRequest.UNSENT;
    cXMLHttpRequest.prototype.responseText  = '';
    cXMLHttpRequest.prototype.responseXML   = null;
    cXMLHttpRequest.prototype.status        = 0;
    cXMLHttpRequest.prototype.statusText    = '';

    // Priority proposal
    cXMLHttpRequest.prototype.priority    = "NORMAL";

    // Instance-level Events Handlers
    cXMLHttpRequest.prototype.onreadystatechange  = null;

    // Class-level Events Handlers
    cXMLHttpRequest.onreadystatechange  = null;
    cXMLHttpRequest.onopen              = null;
    cXMLHttpRequest.onsend              = null;
    cXMLHttpRequest.onabort             = null;

    // Public Methods
    cXMLHttpRequest.prototype.open  = function(sMethod, sUrl, bAsync, sUser, sPassword) {
        // http://www.w3.org/TR/XMLHttpRequest/#the-open-method
        var sLowerCaseMethod = sMethod.toLowerCase();
        if (sLowerCaseMethod == "connect" || sLowerCaseMethod == "trace" || sLowerCaseMethod == "track") {
            // Using a generic error and an int - not too sure all browsers support correctly
            // http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#securityerror, so, this is safer
            // XXX should do better than that, but this is OT to XHR.
            throw new Error(18);
        }

        // Delete headers, required when object is reused
        delete this._headers;

        // When bAsync parameter value is omitted, use true as default
        if (arguments.length < 3) {
            bAsync  = true;
        }

        // Save async parameter for fixing Gecko bug with missing readystatechange in synchronous requests
        this._async   = bAsync;

        // Set the onreadystatechange handler
        var oRequest  = this;
        var nState    = this.readyState;
        var fOnUnload = null;

        // BUGFIX: IE - memory leak on page unload (inter-page leak)
        if (bIE && bAsync) {
            fOnUnload = function() {
                if (nState != cXMLHttpRequest.DONE) {
                    fCleanTransport(oRequest);
                    // Safe to abort here since onreadystatechange handler removed
                    oRequest.abort();
                }
            };
            window.attachEvent("onunload", fOnUnload);
        }

        // Add method sniffer
        if (cXMLHttpRequest.onopen) {
            cXMLHttpRequest.onopen.apply(this, arguments);
        }

        if (arguments.length > 4) {
            this._object.open(sMethod, sUrl, bAsync, sUser, sPassword);
        } else if (arguments.length > 3) {
            this._object.open(sMethod, sUrl, bAsync, sUser);
        } else {
            this._object.open(sMethod, sUrl, bAsync);
        }

        this.readyState = cXMLHttpRequest.OPENED;
        fReadyStateChange(this);

        this._object.onreadystatechange = function() {
            if (bGecko && !bAsync) {
                return;
            }

            // Synchronize state
            oRequest.readyState   = oRequest._object.readyState;
            fSynchronizeValues(oRequest);

            // BUGFIX: Firefox fires unnecessary DONE when aborting
            if (oRequest._aborted) {
                // Reset readyState to UNSENT
                oRequest.readyState = cXMLHttpRequest.UNSENT;

                // Return now
                return;
            }

            if (oRequest.readyState == cXMLHttpRequest.DONE) {
                // Free up queue
                delete oRequest._data;

                // Uncomment these lines for bAsync
                /**
                 * if (bAsync) {
                 *  fQueue_remove(oRequest);
                 * }
                 */

                fCleanTransport(oRequest);

                // Uncomment this block if you need a fix for IE cache
                /**
                 * // BUGFIX: IE - cache issue
                 * if (!oRequest._object.getResponseHeader("Date")) {
                 *  // Save object to cache
                 *  oRequest._cached  = oRequest._object;
                 *
                 *  // Instantiate a new transport object
                 *  cXMLHttpRequest.call(oRequest);
                 *
                 *  // Re-send request
                 *  if (sUser) {
                 *      if (sPassword) {
                 *          oRequest._object.open(sMethod, sUrl, bAsync, sUser, sPassword);
                 *      } else {
                 *          oRequest._object.open(sMethod, sUrl, bAsync);
                 *      }
                 *
                 *      oRequest._object.setRequestHeader("If-Modified-Since", oRequest._cached.getResponseHeader("Last-Modified") || new window.Date(0));
                 *      // Copy headers set
                 *      if (oRequest._headers) {
                 *          for (var sHeader in oRequest._headers) {
                 *              // Some frameworks prototype objects with functions
                 *              if (typeof oRequest._headers[sHeader] == "string") {
                 *                  oRequest._object.setRequestHeader(sHeader, oRequest._headers[sHeader]);
                 *              }
                 *          }
                 *      }
                 *      oRequest._object.onreadystatechange = function() {
                 *          // Synchronize state
                 *          oRequest.readyState   = oRequest._object.readyState;
                 *
                 *          if (oRequest._aborted) {
                 *              //
                 *              oRequest.readyState = cXMLHttpRequest.UNSENT;
                 *
                 *              // Return
                 *              return;
                 *          }
                 *
                 *          if (oRequest.readyState == cXMLHttpRequest.DONE) {
                 *              // Clean Object
                 *              fCleanTransport(oRequest);
                 *
                 *              // get cached request
                 *              if (oRequest.status == 304) {
                 *                  oRequest._object  = oRequest._cached;
                 *              }
                 *
                 *              //
                 *              delete oRequest._cached;
                 *
                 *              //
                 *              fSynchronizeValues(oRequest);
                 *
                 *              //
                 *              fReadyStateChange(oRequest);
                 *
                 *              // BUGFIX: IE - memory leak in interrupted
                 *              if (bIE && bAsync) {
                 *                  window.detachEvent("onunload", fOnUnload);
                 *              }
                 *
                 *          }
                 *      };
                 *      oRequest._object.send(null);
                 *
                 *      // Return now - wait until re-sent request is finished
                 *      return;
                 *  };
                 */

                // BUGFIX: IE - memory leak in interrupted
                if (bIE && bAsync) {
                    window.detachEvent("onunload", fOnUnload);
                }

                // BUGFIX: Some browsers (Internet Explorer, Gecko) fire OPEN readystate twice
                if (nState != oRequest.readyState) {
                    fReadyStateChange(oRequest);
                }

                nState  = oRequest.readyState;
            }
        };
    };

    cXMLHttpRequest.prototype.send = function(vData) {
        // Add method sniffer
        if (cXMLHttpRequest.onsend) {
            cXMLHttpRequest.onsend.apply(this, arguments);
        }

        if (!arguments.length) {
            vData = null;
        }

        // BUGFIX: Safari - fails sending documents created/modified dynamically, so an explicit serialization required
        // BUGFIX: IE - rewrites any custom mime-type to "text/xml" in case an XMLNode is sent
        // BUGFIX: Gecko - fails sending Element (this is up to the implementation either to standard)
        if (vData && vData.nodeType) {
            vData = window.XMLSerializer ? new window.XMLSerializer().serializeToString(vData) : vData.xml;
            if (!this._headers["Content-Type"]) {
                this._object.setRequestHeader("Content-Type", "application/xml");
            }
        }

        this._data = vData;

        /**
         * // Add to queue
         * if (this._async) {
         *  fQueue_add(this);
         * } else { */
        fXMLHttpRequest_send(this);
         /**
         * }
         */
    };

    cXMLHttpRequest.prototype.abort = function() {
        // Add method sniffer
        if (cXMLHttpRequest.onabort) {
            cXMLHttpRequest.onabort.apply(this, arguments);
        }

        // BUGFIX: Gecko - unnecessary DONE when aborting
        if (this.readyState > cXMLHttpRequest.UNSENT) {
            this._aborted = true;
        }

        this._object.abort();

        // BUGFIX: IE - memory leak
        fCleanTransport(this);

        this.readyState = cXMLHttpRequest.UNSENT;

        delete this._data;

        /* if (this._async) {
        *   fQueue_remove(this);
        * }
        */
    };

    cXMLHttpRequest.prototype.getAllResponseHeaders = function() {
        return this._object.getAllResponseHeaders();
    };

    cXMLHttpRequest.prototype.getResponseHeader = function(sName) {
        return this._object.getResponseHeader(sName);
    };

    cXMLHttpRequest.prototype.setRequestHeader  = function(sName, sValue) {
        // BUGFIX: IE - cache issue
        if (!this._headers) {
            this._headers = {};
        }

        this._headers[sName]  = sValue;

        return this._object.setRequestHeader(sName, sValue);
    };

    // EventTarget interface implementation
    cXMLHttpRequest.prototype.addEventListener  = function(sName, fHandler, bUseCapture) {
        for (var nIndex = 0, oListener; oListener = this._listeners[nIndex]; nIndex++) {
            if (oListener[0] == sName && oListener[1] == fHandler && oListener[2] == bUseCapture) {
                return;
            }
        }

        // Add listener
        this._listeners.push([sName, fHandler, bUseCapture]);
    };

    cXMLHttpRequest.prototype.removeEventListener = function(sName, fHandler, bUseCapture) {
        for (var nIndex = 0, oListener; oListener = this._listeners[nIndex]; nIndex++) {
            if (oListener[0] == sName && oListener[1] == fHandler && oListener[2] == bUseCapture) {
                break;
            }
        }

        // Remove listener
        if (oListener) {
            this._listeners.splice(nIndex, 1);
        }
    };

    cXMLHttpRequest.prototype.dispatchEvent = function(oEvent) {
        var oEventPseudo  = {
            'type':             oEvent.type,
            'target':           this,
            'currentTarget':    this,
            'eventPhase':       2,
            'bubbles':          oEvent.bubbles,
            'cancelable':       oEvent.cancelable,
            'timeStamp':        oEvent.timeStamp,
            'stopPropagation':  function() {},  // There is no flow
            'preventDefault':   function() {},  // There is no default action
            'initEvent':        function() {}   // Original event object should be initialized
        };

        // Execute onreadystatechange
        if (oEventPseudo.type == "readystatechange" && this.onreadystatechange) {
            (this.onreadystatechange.handleEvent || this.onreadystatechange).apply(this, [oEventPseudo]);
        }


        // Execute listeners
        for (var nIndex = 0, oListener; oListener = this._listeners[nIndex]; nIndex++) {
            if (oListener[0] == oEventPseudo.type && !oListener[2]) {
                (oListener[1].handleEvent || oListener[1]).apply(this, [oEventPseudo]);
            }
        }

    };

    //
    cXMLHttpRequest.prototype.toString  = function() {
        return '[' + "object" + ' ' + "XMLHttpRequest" + ']';
    };

    cXMLHttpRequest.toString  = function() {
        return '[' + "XMLHttpRequest" + ']';
    };

    /**
     * // Queue manager
     * var oQueuePending = {"CRITICAL":[],"HIGH":[],"NORMAL":[],"LOW":[],"LOWEST":[]},
     * aQueueRunning = [];
     * function fQueue_add(oRequest) {
     *  oQueuePending[oRequest.priority in oQueuePending ? oRequest.priority : "NORMAL"].push(oRequest);
     *  //
     *  setTimeout(fQueue_process);
     * };
     *
     * function fQueue_remove(oRequest) {
     *  for (var nIndex = 0, bFound = false; nIndex < aQueueRunning.length; nIndex++)
     *  if (bFound) {
     *      aQueueRunning[nIndex - 1] = aQueueRunning[nIndex];
     *  } else {
     *      if (aQueueRunning[nIndex] == oRequest) {
     *          bFound  = true;
     *      }
     * }
     *
     *  if (bFound) {
     *      aQueueRunning.length--;
     *  }
     *
     *
     *  //
     *  setTimeout(fQueue_process);
     * };
     *
     * function fQueue_process() {
     * if (aQueueRunning.length < 6) {
     * for (var sPriority in oQueuePending) {
     * if (oQueuePending[sPriority].length) {
     * var oRequest  = oQueuePending[sPriority][0];
     * oQueuePending[sPriority]  = oQueuePending[sPriority].slice(1);
     * //
     * aQueueRunning.push(oRequest);
     * // Send request
     * fXMLHttpRequest_send(oRequest);
     * break;
     * }
     * }
     * }
     * };
     */

    // Helper function
    function fXMLHttpRequest_send(oRequest) {
        oRequest._object.send(oRequest._data);

        // BUGFIX: Gecko - missing readystatechange calls in synchronous requests
        if (bGecko && !oRequest._async) {
            oRequest.readyState = cXMLHttpRequest.OPENED;

            // Synchronize state
            fSynchronizeValues(oRequest);

            // Simulate missing states
            while (oRequest.readyState < cXMLHttpRequest.DONE) {
                oRequest.readyState++;
                fReadyStateChange(oRequest);
                // Check if we are aborted
                if (oRequest._aborted) {
                    return;
                }
            }
        }
    }

    function fReadyStateChange(oRequest) {
        // Sniffing code
        if (cXMLHttpRequest.onreadystatechange){
            cXMLHttpRequest.onreadystatechange.apply(oRequest);
        }


        // Fake event
        oRequest.dispatchEvent({
            'type':       "readystatechange",
            'bubbles':    false,
            'cancelable': false,
            'timeStamp':  new Date().getTime()
        });
    }

    function fGetDocument(oRequest) {
        var oDocument = oRequest.responseXML;
        var sResponse = oRequest.responseText;
        // Try parsing responseText
        if (bIE && sResponse && oDocument && !oDocument.documentElement && oRequest.getResponseHeader("Content-Type").match(/[^\/]+\/[^\+]+\+xml/)) {
            oDocument = new window.ActiveXObject("Microsoft.XMLDOM");
            oDocument.async       = false;
            oDocument.validateOnParse = false;
            oDocument.loadXML(sResponse);
        }

        // Check if there is no error in document
        if (oDocument){
            if ((bIE && oDocument.parseError != 0) || !oDocument.documentElement || (oDocument.documentElement && oDocument.documentElement.tagName == "parsererror")) {
                return null;
            }
        }
        return oDocument;
    }

    function fSynchronizeValues(oRequest) {
        try { oRequest.responseText = oRequest._object.responseText;  } catch (e) {}
        try { oRequest.responseXML  = fGetDocument(oRequest._object); } catch (e) {}
        try { oRequest.status       = oRequest._object.status;        } catch (e) {}
        try { oRequest.statusText   = oRequest._object.statusText;    } catch (e) {}
    }

    function fCleanTransport(oRequest) {
        // BUGFIX: IE - memory leak (on-page leak)
        oRequest._object.onreadystatechange = new window.Function;
    }

    // Internet Explorer 5.0 (missing apply)
    if (!window.Function.prototype.apply) {
        window.Function.prototype.apply = function(oRequest, oArguments) {
            if (!oArguments) {
                oArguments  = [];
            }
            oRequest.__func = this;
            oRequest.__func(oArguments[0], oArguments[1], oArguments[2], oArguments[3], oArguments[4]);
            delete oRequest.__func;
        };
    }

    // Register new object with window
    window.XMLHttpRequest = cXMLHttpRequest;

})();